package com.web.new12306.util;


import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;

import com.web.new12306.common.pojo.SQLParameter;
import com.web.new12306.common.pojo.SQLPrepared;
/**

 * @author 苗宇
 *
 */

public final class DaoUtil {
    private static final DaoUtil INSTANCE = new DaoUtil();

    private static final Map<String , SQLPrepared> SQL_CACHE = new HashMap<>();

    public interface TypeConstants {
        String STRING = String.class.getCanonicalName();

        String FLOAT = Float.class.getCanonicalName();

        String DOUBLE = Double.class.getCanonicalName();

        String INTEGER = Integer.class.getCanonicalName();

        String LONG = Long.class.getCanonicalName();

        String BOOLEAN = Boolean.class.getCanonicalName();

        String DATE = Date.class.getCanonicalName();

        // Parameter Types

        String MAP = Map.class.getCanonicalName();
    }

    private DaoUtil() {
        super();
    }

    public static DaoUtil getInstance() {
        return INSTANCE;
    }

    public synchronized static void updateCache(Map<String , SQLPrepared> cache) {
        SQL_CACHE.clear();
        SQL_CACHE.putAll(cache);
    }

    /**
     * 获取一个 DataSource Connection
     * <p/>
     * 便于开发者直接与 DATABASE 交互
     * @return
     */
    public static Connection getConnection() {
        Connection connection = null;

        try {
            Context context = new InitialContext();
            DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/new12306");
            connection = dataSource.getConnection();
        } catch (NamingException | SQLException e) {
            e.printStackTrace();
        }

        return connection;
    }

    private class FiledData {
        private String type;

        private Object value;

        public FiledData(String type, Object value) {
            this.type = type;
            this.value = value;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }

    private FiledData findFinalChild(Object param , SQLParameter sqlParameter) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String fieldTypeName = null;
        Object fieldValue = null;

        if (param instanceof Map) {
            Map<String , Object> parameter = (Map<String, Object>)param;
            String paramName = sqlParameter.getValue();
            fieldValue = parameter.get(paramName);
            fieldTypeName = fieldValue.getClass().getCanonicalName();

        }
        else {
            Class<?> clazz = param.getClass();
            String paramName = sqlParameter.getValue();
            Field field = clazz.getField(paramName);
            fieldTypeName = field.getClass().getCanonicalName();
            String fieldGetterName = "get" + StringUtils.capitalize(paramName);
            Method filedGetter = clazz.getMethod(fieldGetterName);
            fieldValue = filedGetter.invoke(param);
        }

        if (sqlParameter.getMember() == null) {
            return new FiledData(fieldTypeName , fieldValue);
        }
        else {
            return this.findFinalChild((Map)fieldValue , sqlParameter.getMember());
        }
    }

    private void fillPrepareStatement(PreparedStatement preparedStatement , List<SQLParameter> paramKey , Object param) {
        int size = paramKey.size();
        SQLParameter sqlParameter = null;
        FiledData fieldData = null;
        String type = null;

        try {
            for (int i = 0 ; i < size ; i++) {
                sqlParameter = paramKey.get(i);
                fieldData = this.findFinalChild(param , sqlParameter);
                type = fieldData.getType();

                if (TypeConstants.STRING.equals(type)) {
                    preparedStatement.setString(i , (String)fieldData.getValue());
                }
                else if (TypeConstants.FLOAT.equals(type)) {
                    preparedStatement.setFloat(i , (Float)fieldData.getValue());
                }
                else if (TypeConstants.DOUBLE.equals(type)) {
                    preparedStatement.setDouble(i , (Double)fieldData.getValue());
                }
                else if (TypeConstants.INTEGER.equals(type)) {
                    preparedStatement.setInt(i , (Integer)fieldData.getValue());
                }
                else if (TypeConstants.LONG.equals(type)) {
                    preparedStatement.setLong(i , (Long)fieldData.getValue());
                }
                else if (TypeConstants.BOOLEAN.equals(type)) {
                    preparedStatement.setBoolean(i , (Boolean)fieldData.getValue());
                }
                else if (TypeConstants.DATE.equals(type)) {
                    preparedStatement.setDate(i , new java.sql.Date(((Date)fieldData.getValue()).getTime()));
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Integer exec(String sqlId , Object param){
        SQLPrepared sqlPrepared = SQL_CACHE.get(sqlId);
        List<SQLParameter> sqlParameters = sqlPrepared.getParameters();
        Connection connection = this.getConnection();

        Integer rows = 0;

        try (PreparedStatement preparedStatement = connection.prepareStatement(sqlPrepared.getPrepared())) {
            this.fillPrepareStatement(preparedStatement , sqlParameters , param);
            rows = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return rows;
    }

    public Integer insert(String sqlId , Object param) {
        return this.exec(sqlId , param);
    }

    public Integer delete(String sqlId, Object param) {
        return this.exec(sqlId , param);

    }
    public Integer update(String sqlId, Object param) {
        return this.exec(sqlId , param);
    }

    public <T> T get(String sqlId , Class<T> clazz , Object param) {
        T result = null;
        try {
            result = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        SQLPrepared sqlPrepared = SQL_CACHE.get(sqlId);
        List<SQLParameter> sqlParameters = sqlPrepared.getParameters();
        Connection connection = this.getConnection();

        try (PreparedStatement preparedStatement = connection.prepareStatement(sqlPrepared.getPrepared())) {
            this.fillPrepareStatement(preparedStatement , sqlParameters , param);
            ResultSet rs = preparedStatement.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return result;
    }

    public <T> List<T> getList(String sqlId , Class<T> clazz , Object param) {
        List<T> result = new ArrayList<>();
        T object = null;
        try {
            object = clazz.newInstance();
            result.add(object);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        SQLPrepared sqlPrepared = SQL_CACHE.get(sqlId);
        List<SQLParameter> sqlParameters = sqlPrepared.getParameters();
        Connection connection = this.getConnection();

        try (PreparedStatement preparedStatement = connection.prepareStatement(sqlPrepared.getPrepared())) {
            this.fillPrepareStatement(preparedStatement , sqlParameters , param);
            preparedStatement.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return result;
    }
}
